# !/usr/bin/env python3
# -*- coding: utf-8 -*-

import math
import xml.etree.ElementTree as EleTree
from util.lib import pretty_xml
from util.topo_and_streams_txt_parser_for_frame_demo import init_topo_and_stream_obj_set_for_frame_demo


# 求所有流量的调度周期
def compute_hyper_period(*args):
    # period_set = []
    # for stream_obj in stream_obj_set:
    #     period_set.append(stream_obj.period)
    hyper_period = 1
    for period in args:
        hyper_period = int(period) * int(hyper_period) / math.gcd(int(period), int(hyper_period))
    return int(hyper_period)


def transforming_solution(plan_txt, plan_xml):
    """
    将OpenPlanner输出的plan_txt转换成TSNLight的plan_xml
    :param plan_txt:
    :param plan_xml:
    :return:
    """
    # 第一步，从plan_txt中读取
    # 每一条链路的(offset, flow_id, prio_queues)
    plan_set = parsing_plan_txt(plan_txt)
    # print(plan_set)

    # 第二步，将plan_set中的内容转化成xml
    generating_plan_xml(plan_set, plan_xml)
    return


def generating_plan_xml(plan_set, plan_xml):
    root = EleTree.Element('plan')
    for plan_each_link in plan_set:
        node = EleTree.Element('link')
        node_link_id = EleTree.SubElement(node, 'link_id')
        node_link_id.text = plan_each_link['link_id']

        entry_set = plan_each_link['entry_set']
        for entry in entry_set:
            node_entry = EleTree.SubElement(node, 'entry')
            node_macrotick = EleTree.SubElement(node_entry, 'macrotick_id')
            node_macrotick.text = str(entry['macrotick_id'])
            node_flow_id = EleTree.SubElement(node_entry, 'flow_id')
            node_flow_id.text = str(entry['flow_id'])
            node_st_queue_id = EleTree.SubElement(node_entry, 'st_queue_id')
            node_st_queue_id.text = str(entry['st_queue_id'])

        root.append(node)

    tree = EleTree.ElementTree(root)
    pretty_xml(root, '\t', '\n')  # 执行美化方法
    tree.write(plan_xml,
               encoding='utf-8',
               xml_declaration=True)
    return


def parsing_plan_txt(plan_txt):
    """
    解析plan_txt文件
    :param plan_txt:
    :return:
    """
    plan_set = []
    fd = open(plan_txt, 'r')
    data = fd.readline()
    entry_set_each_link = []
    link_id = 0
    while data:
        # 开始一条新的链路
        if 'offset and prio set' in data:
            if entry_set_each_link:
                plan_set.append(
                    {'link_id': link_id,
                     'entry_set': entry_set_each_link}
                )
            entry_set_each_link = []
            # 提取链路id
            link_id = data.split(':')[1].split()[0]
            # print(link_id)
            data = fd.readline()

        # 开始一条新的流
        if '----' in data:
            data = fd.readline()
            if data != '\n':
                # 提取流id
                flow_id = data.split("^")[0].split("_")[1]
                # print(flow_id)
                offset_set = []
                offset = data.split("=")[1].split()[0]
                offset_set.append(offset)
                data = fd.readline()
                # 读取完这条流所有的offset
                while 'offset' in data:
                    offset = data.split("=")[1].split()[0]
                    offset_set.append(offset)
                    data = fd.readline()
                # 读取这条流的prio_queues
                prio_queues = data.split("=")[1].split()[0]

                # flow_id offset_set prio_queues组装成这条流所有的entry
                # 并将所有entry添加到这条链路的entry_set中
                for offset in offset_set:
                    entry_set_each_link.append(
                        {'macrotick_id': int(offset),
                         'flow_id': int(flow_id),
                         'st_queue_id': int(prio_queues)}
                    )
                entry_set_each_link.sort(key=lambda x: x['macrotick_id'])
        data = fd.readline()
    plan_set.append(
        {'link_id': link_id,
         'entry_set': entry_set_each_link}
    )
    fd.close()

    return plan_set


def translating_solution(topo_txt, stream_txt, solution_txt, trans_sched_txt):

    # 判断是否有解
    fd = open(solution_txt, 'r')
    sat_flag = fd.readline()
    if sat_flag.split()[0] == 'unsat':
        print('当前流量和拓扑配置无可行解')
        exit(0)

    # 初始化link_obj_set, stream_obj_set, stream_instance_obj_set
    (link_obj_set,
     stream_obj_set,
     stream_instance_obj_set) = init_topo_and_stream_obj_set_for_frame_demo(topo_txt,
                                                                            stream_txt)
    # 计算调度周期
    hyper_period = compute_hyper_period(*[int(stream_obj.period) for stream_obj in stream_obj_set])

    fr = open(solution_txt, 'r')
    fw = open(trans_sched_txt, 'w')

    # 从solution_txt文本文件读取解
    data = fr.readline()
    while data:
        # 判断是否是某一条流的offset值
        if "offset:" not in data:
            # 若不是，直接将该字符串写入trans_sched_txt文件
            fw.write(data)
        elif "offset:" in data:
            # 若是，为这条流量计算每一个帧实例的offset
            # 第一步，提取该流的stream_id, link_id, offset, 以及name
            stream_id = data.split('_')[1].split('^')[0]
            stream_id = int(stream_id)
            link_id = data.split('(')[1].split(')')[0]
            link_id = int(link_id)
            offset = data.split('=')[1].split()[0]
            offset = int(offset)
            name = data.split()[1]

            # 第二步，提取出这条流的period，以及这条链路的macrotick
            period = stream_obj_set[stream_id].period
            macrotick = link_obj_set[link_id].macrotick

            # 第三步，计算每一个流实例的offset
            for i in range(0, hyper_period, period):
                # 第i个帧实例的offset计算公式：offset+i*period/macrotick
                # 注意：不能使用offset(i-1)+period/macrotick的方式计算，这样会累计误差
                offset_i = offset + math.ceil(i / macrotick)
                name_i = name + '_' + str(int(i/period))
                # 第四步：将该流实例的offset写入文本文件
                fw.write('{:>15}{:^20}{}{:^15}{}\n'.format('offset:', name_i, '=', offset_i, '|'))

            # 第五步，将该流的prio_queues写入文本文件
            data = fr.readline()
            fw.write(data)

        data = fr.readline()

    fr.close()
    fw.close()


def get_plan_set(plan_xml):
    plan_set = []
    tree = EleTree.parse(plan_xml)
    link_ele_set = tree.findall('link')

    for link_ele in link_ele_set:
        link_id = link_ele.find('link_id').text
        link_id = int(link_id)
        entry_ele_set = link_ele.findall('entry')
        for entry_ele in entry_ele_set:
            macrotick_id = entry_ele.findtext('macrotick_id')
            macrotick_id = int(macrotick_id)
            flow_id = entry_ele.findtext('flow_id')
            flow_id = int(flow_id)
            priority = entry_ele.findtext('st_queue_id')
            # 0对应队列7，1对应队列6，2对应队列5
            priority = 7 - int(priority)
            plan_set.append({'link_id': link_id,
                             'macrotick_id': macrotick_id,
                             'flow_id': flow_id,
                             'priority': priority
                            })

    return plan_set


def parse_plan_xml(plan_xml):
    tree = EleTree.parse(plan_xml)
    link_ele_set = tree.findall('link')
    plan_set = []

    for link_ele in link_ele_set:
        plan_entry = {}
        link_id = link_ele.findtext('link_id')
        plan_entry.update(
            {'link_id': int(link_id)}
        )
        entry_ele_set = link_ele.findall('entry')
        scheduling_set = []
        for entry_ele in entry_ele_set:
            macrotick_id = entry_ele.findtext('macrotick_id')
            flow_id = entry_ele.findtext('flow_id')
            priority = entry_ele.findtext('st_queue_id')
            # 0对应队列7，1对应队列6，2对应队列5
            priority = 7 - int(priority)
            scheduling_set.append(
                {
                    'macrotick_id': int(macrotick_id),
                    'flow_id': int(flow_id),
                    'priority': priority
                }
            )
        if scheduling_set:
            plan_entry.update(
                {'scheduling_set': scheduling_set}
            )
            plan_set.append(plan_entry)

    return plan_set

